import { director } from "cc";
import { GameEvent } from "./GameDef";
import { SnakeCmp } from "./ecs/cmp/SnakeCmp";
import { SnakeFactory } from "./ecs/factory/SnakeFactory";
import { ColliderMgr } from "./ecs/scmp/ColliderMgr";
import { WorldMgr } from "./ecs/scmp/WorldMgr";
import { AISys } from "./ecs/sys/AISys";
import { ChunkSys } from "./ecs/sys/ChunkSys";
import { ColliderSys } from "./ecs/sys/ColliderSys";
import { FoodSys } from "./ecs/sys/FoodSys";
import { MoveSys } from "./ecs/sys/MoveSys";
import { SnakeSys } from "./ecs/sys/SnakeSys";
import { ECSScene, ECSSceneEvent } from "./lib/kgame/ecs/ECSScene";
import { ISize } from "./lib/kgame/util/Defined";
import { RandUtil } from "./lib/kgame/util/Rand";
import { GameConn } from "./player/GameConn";
import { GamePlayer } from "./player/GamePlayer";

export class Game {

    private _tick: any;
    scene = new ECSScene(30);
    world = new WorldMgr(5000, 5000, 1000);
    playerMap = new Map<number, GamePlayer>();
    snakeMap = new Map<number, SnakeCmp>();
    curPid = 0;

    msgs: kp_com_msg[] = [];
    private _addMsgs: kp_com_msg[] = [];

    private _addSnakes: kp_snake_snake[] = [];
    private _updateSnakes: kp_snake_snake[] = [];
    private _removeSnakeIds: number[] = [];

    private _updateScoreTime = 0;

    start() {
        (window as any)["game"] = this;
        this.scene.setCustomLoop(
            tick => {
                this._tick = tick;
                director.getScheduler().schedule(tick, director.getScene(), 0)
            },
            () => {
                director.getScheduler().unschedule(this._tick, director.getScene());
            });
        this.scene.on(ECSSceneEvent.afterUpdate, this.onUpdate, this);

        this.scene.addCmp(this.world);
        this.scene.addCmp(new ColliderMgr(this.world));

        this.scene.addSys(FoodSys);
        this.scene.addSys(MoveSys);
        this.scene.addSys(SnakeSys);
        this.scene.addSys(ColliderSys);
        this.scene.addSys(AISys);
        this.scene.addSys(ChunkSys);

        this.scene.on(GameEvent.snakeSpawn, data => {
            const snake = data;
            if (!this.snakeMap.get(snake.entityId)) {
                this._addSnakes.push(this.getSnakeInfo(snake));
            } else {
                this._updateSnakes.push(this.getSnakeInfo(snake));
            }
            this.snakeMap.set(snake.snakeId, data);

            this.msgs.push();
        });

        this.scene.on(GameEvent.snakeDead, data => {
            const snake = data;
            this._updateSnakes.push({
                id: snake.entityId,
                score: 0
            });

            if (snake.killer instanceof SnakeCmp) {
                this.addMsg(RandUtil.choose([
                    `【${snake.name}】一头撞上了【${snake.killer.name}】！`,
                    `【${snake.name}】狠狠地撞上了【${snake.killer.name}】！`,
                    `【${snake.name}】与【${snake.killer.name}】撞了个满怀！`,
                    `【${snake.name}】与【${snake.killer.name}】融为一体！`,
                ]));
            } else if (snake.killer === "edge") {
                this.addMsg(RandUtil.choose([
                    `【${snake.name}】一头撞在了墙上！`,
                    `【${snake.name}】狠狠地撞在了墙上！`,
                    `铁头娃【${snake.name}】不相信墙是硬的！`,
                ]));
            } else {
                this.addMsg(`${snake.name}死于神秘力量！`);
            }
        });

        this.scene.on(GameEvent.snakeRemove, data => {
            const snakeId = data;
            this.snakeMap.delete(snakeId);
            this._removeSnakeIds.push(snakeId);
        });

        this.scene.on(GameEvent.snakeGhost, data => {
            const { head } = data;
            this._updateSnakes.push({
                id: data.entityId,
                ghost: head.ghostTime > 0 ? 1 : 0
            });

        });

        this.scene.start();
        //创建AI
        for (let i = 0; i < 20; ++i) {
            SnakeFactory.createAiSnake(this.scene);
        }

        return this;
    }

    onUpdate() {
        //通知蛇添加
        if (this._addSnakes.length > 0) {
            this.playerMap.forEach(player => {
                player.onSnakeAdd(this._addSnakes);
            });
            this._addSnakes.length = 0;
        }

        //通知蛇更新
        if (this._updateSnakes.length > 0) {
            this.playerMap.forEach(player => {
                player.onSnakeUpdate(this._updateSnakes);
            });
            this._updateSnakes.length = 0;
        }

        //通知玩家场景实体差异变化
        this.playerMap.forEach(player => {
            player.update();
        });

        //通知更新所有蛇分数
        const now = this.scene.runTime;
        if (now >= this._updateScoreTime) {
            this._updateScoreTime = now + 1;
            const updateSnakes: kp_snake_snake[] = [];
            this.snakeMap.forEach(snake => {
                if (!snake.death) {
                    updateSnakes.push({
                        id: snake.snakeId,
                        score: snake.head.score
                    });
                }
            })

            if (updateSnakes.length > 0) {
                this.playerMap.forEach(player => {
                    player.onSnakeUpdate(updateSnakes);
                });
            }
        }

        //通知消息添加
        if (this._addMsgs.length > 0) {
            this.playerMap.forEach(player => {
                player.conn.send(KProtoId.msg_add, { msgs: this._addMsgs });
            });
            this._addMsgs.length = 0;
        }

        //通知蛇移除
        if (this._removeSnakeIds.length > 0) {
            this.playerMap.forEach(player => {
                player.conn.send(KProtoId.snakeRemove, { ids: this._removeSnakeIds })
            });
            this._removeSnakeIds.length = 0;
        }
    }

    onConn(conn: GameConn, onJoin?: (pid: number) => void) {
        conn.on(KProtoId.joinGame, data => {
            const pid = ++this.curPid;
            if (onJoin) {
                onJoin(pid);
            }
            conn.on(KProtoId.ready, () => {
                const snake = this.addPlayer(conn, pid, data.name, data.screen);
                conn.send(KProtoId.ready, { snakeId: snake.snake.entityId });
            });
            conn.send(KProtoId.joinGame, {
                pid: pid,
                worldSize: this.world.rect
            });
        });
    }

    addPlayer(conn: GameConn, pid: number, name: string, screen: ISize) {
        const player = new GamePlayer(this, conn, pid, name, screen);
        this.playerMap.set(pid, player);
        this.addMsg(`${player.name}加入了游戏！`);
        return player;
    }

    removePlayer(pid: number) {
        const player = this.playerMap.get(pid);
        if (player) {
            this.snakeMap.delete(player.snake.snakeId);
            this.playerMap.delete(pid);
            player.onRemove();
            this.addMsg(`${player.name}退出了游戏！`);
            return true;
        }
        return false;
    }

    getSnakeInfo(snake: SnakeCmp): kp_snake_snake {
        const { head } = snake;
        return {
            id: snake.entityId,
            name: snake.name,
            colors: head.colors,
            score: head.score,
            ghost: head.ghostTime > 0 ? 1 : 0
        };
    }

    addMsg(msg: string) {
        const data: kp_com_msg = {
            time: Date.now(),
            msg
        };
        this.msgs.push(data);
        this._addMsgs.push(data);

        if (this.msgs.length > 25) {
            this.msgs.splice(0, this.msgs.length - 25);
        }
    }

}